﻿#include "CDBConnector.h"
#include <QSqlQuery>
#include <QSqlDatabase>
#include <QFile>
#include <QSqlError>
#include <QSqlDriver>
#include "glog/logging.h"

using namespace zl;

#define GETDBERRORSTR(DB) DB.lastError().text().toStdString()

CDBConnector* zl::CDBConnector::instance_ = nullptr;
CDBConnector * zl::CDBConnector::Instance()
{
	if (instance_ == nullptr)
	{
		instance_ = new CDBConnector;
	}

	return instance_;
}

void zl::CDBConnector::Release()
{
	LOG(WARNING) << "release db connector...";
	instance_->Uninitialize();
	delete instance_;
	instance_ = nullptr;
}

int32_t zl::CDBConnector::Initialize(const DBConfig & conf)
{
	LOG(INFO) << "open database, type: " << conf.type;
	config_ = conf;

	if (conf.type == CDBConnector::EDBType_MYSQL)
	{
		db_ = QSqlDatabase::addDatabase("QMYSQL");
		db_.setHostName(conf.addr.c_str());
		db_.setUserName(conf.user.c_str());
		db_.setPassword(conf.pwd.c_str());
		db_.setConnectOptions("MYSQL_OPT_RECONNECT=1");
        db_.exec("SET NAMES UTF8");
	}
	else if (conf.type == EDBType_SQLITE3)
	{
        db_ = QSqlDatabase::addDatabase("QSQLITE");
        db_.setDatabaseName("cabinet_storage.db");
		db_.setUserName(conf.user.c_str());
		db_.setPassword(conf.pwd.c_str());
	}
	else
	{
		LOG(ERROR) << "unsupported database type: " << conf.type;
		return EDBError_DatabaseError;
	}

	// open database
	if (!db_.open())
	{
		LOG(ERROR) << "Open Database Error: " << GETDBERRORSTR(db_);
		return EDBError_ConnectFailed;
	}

	int32_t ret = init_database();
	if (ret != CDBConnector::EDBError_Success)
	{
		LOG(ERROR) << "init database error: " << GETDBERRORSTR(db_);
		return EDBError_InitDatabaseFailed;
	}

	return EDBError_Success;
}

int32_t zl::CDBConnector::Uninitialize()
{
	LOG(INFO) << "close database";
	return EDBError_Success;
}

QSqlQuery zl::CDBConnector::CreateQuery()
{
	return QSqlQuery(db_);
}

int32_t zl::CDBConnector::ExecQuery(QSqlQuery& query)
{
    try
    {
        begin_transaction();
        if (!query.exec())
        {
            LOG(ERROR) << "exec query failed :" << GETDBERRORSTR(query);
            query_rollback();
            return EDBError_ExecQueryFailed;
        }

        query_commit();
    }
    catch(std::exception& e)
    {
        LOG(ERROR) << "exec query exception:" << e.what();
        return EDBError_ExecQueryFailed;
    }

	return EDBError_Success;
}

int32_t zl::CDBConnector::ExecBatchQuery(QSqlQuery & query)
{
	begin_transaction();
	if (!query.execBatch())
	{
		LOG(ERROR) << "exec query failed :" << GETDBERRORSTR(query);
		query_rollback();
		return EDBError_ExecQueryFailed;
	}

	query_commit();
	return EDBError_Success;
}

int32_t zl::CDBConnector::ExecSql(const QString &sql)
{
    begin_transaction();
    // exec query
    for (const QString& s : sql.split(';'))
    {
        if (s.trimmed().isEmpty())
        {
            continue;
        }

        QSqlQuery query(db_);
        if (!query.exec(s))
        {
            LOG(ERROR) << "exec sql query failed:" << GETDBERRORSTR(query);
            LOG(ERROR) << "query string:" << s.toStdString();
            return EDBError_ExecQueryFailed;
        }
    }

    query_commit();
    return EDBError_Success;
}

int32_t zl::CDBConnector::init_database()
{
	LOG(INFO) << "init database";

    int32_t ret = EDBError_Undefined;
    switch (config_.type)
    {
    case EDBType_MYSQL:
        ret = init_database_mysql();
        break;
    case EDBType_SQLITE3:
        ret = init_database_sqlite3();
        break;
    default:
        LOG(ERROR) << "unknow database type: " << config_.type;
        return EDBError_Undefined;
    }

    if (ret != EDBError_Success)
    {
        LOG(ERROR) << "init database failed: " << GETDBERRORSTR(db_);
        return EDBError_InitDatabaseFailed;
    }

	// insert default card
    QSqlQuery query(db_);
    query.prepare("SELECT * FROM T_MANAGER_CARD");
    if (query.exec())
	{
        if (!query.next())
		{
			db_.exec("INSERT INTO T_MANAGER_CARD (CARDNO, STATUS) VALUES('72543100', 1);");
            LOG(INFO) << "insert default cardno: " << GETDBERRORSTR(db_);
		}
	}

    LOG(INFO) << "init database success " << GETDBERRORSTR(db_);
	return EDBError_Success;
}

int32_t zl::CDBConnector::init_database_mysql()
{
    QFile sql(":/sql/locker_mysql.sql");
    if (!sql.open(QIODevice::ReadOnly))
    {
        LOG(ERROR) << "Init database failed, load sql res failed";
        return EDBError_InitDatabaseFailed;
    }

    // exec query
    auto content_sql = sql.readAll();
    QSqlQuery query(db_);
    // query.prepare(sql.readAll());
    if (!query.exec(content_sql))
    {
        LOG(ERROR) << "Init database failed, exec sql failed: " << GETDBERRORSTR(query);
        return EDBError_InitDatabaseFailed;
    }

    return EDBError_Success;
}

int32_t zl::CDBConnector::init_database_sqlite3()
{
    QFile sql(":/sql/locker_sqlite3.sql");
    if (!sql.open(QIODevice::ReadOnly))
    {
        LOG(ERROR) << "Init database failed, load sql res failed";
        return EDBError_InitDatabaseFailed;
    }

    // exec query
    QString content_sql = sql.readAll();
    for (const QString& s : content_sql.split(';'))
    {
        if (s.trimmed().isEmpty())
        {
            continue;
        }

        QSqlQuery query(db_);
        if (!query.exec(s))
        {
            LOG(ERROR) << "exec sql query failed:" << GETDBERRORSTR(query);
            LOG(ERROR) << "query string:" << s.toStdString();
            return EDBError_ExecQueryFailed;
        }
    }

    return EDBError_Success;
}

void zl::CDBConnector::begin_transaction()
{
    if (config_.type == EDBType_MYSQL)
    {
        db_.exec("START TRANSACTION");
    }
    else if (config_.type == EDBType_SQLITE3)
    {
        db_.transaction();
    }
}

void zl::CDBConnector::query_commit()
{
    if (config_.type == EDBType_MYSQL)
    {
        db_.exec("COMMIT");
    }
    else if (config_.type == EDBType_SQLITE3)
    {
        db_.commit();
    }
}

void zl::CDBConnector::query_rollback()
{
    if (config_.type == EDBType_MYSQL)
    {
        db_.exec("ROLLBACK");
    }
    else if (config_.type == EDBType_SQLITE3)
    {
        db_.rollback();
    }
}

void zl::CDBConnector::reconnect_database()
{
    // todo
}
